//D0 = GPIO16; 
//D1 = GPIO5; 
//D2 = GPIO4;	LED on esp8266
//D3 = GPIO0;can not download when connected to low
//D4 = GPIO2;	
//D5 = GPIO14;  
//D6 = GPIO12;
//D7 = GPIO13;
//D8 = GPIO15;  can not start when high input
//D9 = GPIO3; UART RX
//D10 = GPIO1; UART TX
//LED_BUILTIN = GPIO16 (auxiliary constant for the board LED, not a board pin);


#include<ESP8266WiFi.h>
#include<WiFiUdp.h>
#include<ESP8266mDNS.h>
#include<ArduinoOTA.h>
#include<ESP8266WiFiMulti.h>
#include<ESP8266WebServer.h> 
#include<FS.h>  
#include<time.h>
#define timezone 8


//一定要修改这里，使用自己的wifi信息
#include "D:\GitHub\Private\wifi_config.h"//这个文件保存着我自己家wifi的用户名和密码，注释掉这一行。
//const char* ssid = "wang";  //Wifi名称
//const char* password = "12345678";  //Wifi密码

ESP8266WebServer WebServer(80);
WiFiUDP m_WiFiUDP;
#define UDP_BUF_SIZE 100
char UdpRxBuf[UDP_BUF_SIZE];
char DateTimeStr[20];

#include "D:\GitHub\Gitee\HomeAutomation\ESP8266Web\struct.h"
tLightData LightData[LIGHT_CONTROLER_NUMBER];
tLightCommand LightCommand[LIGHT_CONTROLER_NUMBER];
int LastLightUpdate[LIGHT_CONTROLER_NUMBER];

//根据MAC地址，查询节点序号,这里保存所有节点的MAC地址。
//需要自行把所有节点的MAC地址添加到这里
unsigned long LightMacAddress[LIGHT_CONTROLER_NUMBER][6]
= {
	{0x52,0x02,0x91,0xFD,0xF4,0xE6}
	,{0x52,0x02,0x91,0xFD,0x7D,0x45}
	,{0x00,0x00,0x00,0x00,0x00,0x02}
};

unsigned long TenthSecondsSinceStart = 0;
void TenthSecondsSinceStartTask();
void OnTenthSecond();
void OnSecond();
void StartWebServer();
bool SearchMacAddress(unsigned char* Id, unsigned long* Mac, unsigned long* MacList, unsigned long MaxNumber);
void ProcessNode();

void setup() 
{       
	delay(50);                      
	Serial.begin(115200);

	WiFi.disconnect();
	WiFi.mode(WIFI_STA);//设置模式为STA
	byte mac[6];
	WiFi.softAPmacAddress(mac);
	printf("macAddress 0x%02X:0x%02X:0x%02X:0x%02X:0x%02X:0x%02X\r\n",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);

	m_WiFiUDP.begin(5050);

	//连接WiFi
	Serial.print("Is connection routing, please wait"); 

	//设置固定IP地址
	IPAddress staticIP(192, 168, 0, 21); //ESP static ip
	IPAddress gateway(192, 168, 0, 1);   //IP Address of your WiFi Router (Gateway)
	IPAddress subnet(255, 255, 255, 0);  //Subnet mask
	IPAddress dns(192, 168, 0, 1);  //DNS
	WiFi.config(staticIP, gateway, subnet, dns);

	WiFi.begin(ssid, password); //Wifi接入到网络
	Serial.println("\nConnecting to WiFi");
	//如果Wifi状态不是WL_CONNECTED，则表示连接失败
	unsigned char WiFiTimeOut = 0;
	while (WiFi.status() != WL_CONNECTED) {  
		Serial.print("."); 
		delay(1000);    //延时等待接入网络
		WiFiTimeOut++;
		if (WiFiTimeOut>10)
		{
			break;
			Serial.println("\nConnecting to WiFi Failed");
		}
	}

	//设置时间格式以及时间服务器的网址
	configTime(timezone * 3600, 0, "pool.ntp.org", "time.nist.gov");
	Serial.println("\nWaiting for time");
	while (!time(nullptr)) {
		Serial.print(".");
		delay(1000);    
	}
	Serial.println("");


	//以下是启动OTA，可以通过WiFi刷新固件
	ArduinoOTA.onStart([]() {
		String type;
		if (ArduinoOTA.getCommand() == U_FLASH) {
			type = "sketch";
		} else { // U_SPIFFS
			type = "filesystem";
		}

		// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
		Serial.println("Start updating " + type);
	});
	ArduinoOTA.onEnd([]() {
		Serial.println("\nEnd");
	});
	ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
		Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
	});
	ArduinoOTA.onError([](ota_error_t error) {
		Serial.printf("Error[%u]: ", error);
		if (error == OTA_AUTH_ERROR) {
			Serial.println("Auth Failed");
		} else if (error == OTA_BEGIN_ERROR) {
			Serial.println("Begin Failed");
		} else if (error == OTA_CONNECT_ERROR) {
			Serial.println("Connect Failed");
		} else if (error == OTA_RECEIVE_ERROR) {
			Serial.println("Receive Failed");
		} else if (error == OTA_END_ERROR) {
			Serial.println("End Failed");
		}
	});
	ArduinoOTA.begin();
	Serial.println("Ready");
	Serial.print("IP address: ");
	Serial.println(WiFi.localIP());

	// 启动闪存文件系统
	if (SPIFFS.begin()) 
	{
		Serial.println("SPIFFS Started.");
	}
	else 
	{
		Serial.println("SPIFFS Failed to Start.");
	}

	StartWebServer();

}

void loop() 
{
	ArduinoOTA.handle();
	WebServer.handleClient();
	TenthSecondsSinceStartTask();


	//接受节点发来的数据
	m_WiFiUDP.parsePacket(); 
	unsigned int UdpAvailable = m_WiFiUDP.available();
	if (UdpAvailable != 0)
	{
		//printf("UDP got %d bytes\r\n",UdpAvailable);

		if (UdpAvailable > UDP_BUF_SIZE)
		{
			printf("UDP_BUF_SIZE not enough\r\n");
		}
		else
		{
			m_WiFiUDP.read(UdpRxBuf, UdpAvailable);
			ProcessNode();
		}
	}
}

void PocessControl(int DeviceType, int DeviceIndex, int Operation)
{
	String ReturnString;

	//printf("Control DeviceType:%d DeviceIndex:%d Operation:%d \r\n"，DeviceType，DeviceIndex，Operation);

	if (DeviceType == DEVICE_TYPE_LIGHT)
	{
		if (DeviceIndex < LIGHT_CONTROLER_NUMBER)
		{
			int LightIndex = Operation / 3;

			ReturnString = DateTimeStr;
			ReturnString += " ";

			if (Operation % 3 == OPERATION_ON)
			{
				LightCommand[DeviceIndex].LightSetTo[LightIndex] = true;
				ReturnString += "房间 " + String(DeviceIndex + 1) + " 灯 " + String(LightIndex + 1) + " 打开。请等待状态更新。";
			}
			else if (Operation % 3 == OPERATION_OFF)
			{
				LightCommand[DeviceIndex].LightSetTo[LightIndex] = false;
				ReturnString += "房间 " + String(DeviceIndex + 1) + " 灯 " + String(LightIndex + 1) + " 关闭。请等待状态更新。";
			}
			else if (Operation % 3 == OPERATION_ON_OFF)
			{
				LightCommand[DeviceIndex].LightSetTo[LightIndex] = !LightData[DeviceIndex].LightOn[LightIndex];
				ReturnString += "房间 " + String(DeviceIndex + 1) + " 灯 " + String(LightIndex + 1) + " 开关。请等待状态更新。";
			}

			//设置标志，让通知节点终端控制开关。
			LightCommand[DeviceIndex].LightNeedSet[LightIndex] = true;
		}

		WebServer.send(200, "text/plain", ReturnString);
	}
}

String ProcessUpdate()
{
	//自动生成一串用“,”隔开的字符串。
	//HTML脚本会按照“, ”分割，形成一个字符串数组。
	//并把这个数组填个表格的相应部分。
	String ReturnString;
	ReturnString = DateTimeStr;

	for (byte i = 0; i < LIGHT_CONTROLER_NUMBER; i++)
	{
		ReturnString += ",";

		for (byte j = 0; j < LIGHT_NUMBER; j++)
		{
			ReturnString += (LightData[i].LightOn[j]?"<font color=\"#FF0000\">开</font>":"<font color=\"#00FF00\">关</font>");
			ReturnString += " ";
		}
		
		ReturnString += "超时";
		ReturnString += String(LastLightUpdate[i]);
	}

	return ReturnString;
}

void ProcessNode()
{
	unsigned long* pDataType = (unsigned long*)UdpRxBuf;
	unsigned char DeviceIndex;

	//printf("DataType:%d\r\n", *pDataType);

	switch (*pDataType)
	{
	case DEVICE_TYPE_LIGHT:
		struct tLightData * pLightData;
		pLightData  = (struct tLightData *)UdpRxBuf;
		//根据MAC地址，查询节点序号
		if (SearchMacAddress(&DeviceIndex, pLightData->Mac, &LightMacAddress[0][0], LIGHT_CONTROLER_NUMBER))
		{
			printf("Found in Mac list:%d\r\n", DeviceIndex);
			//保存到对应的节点信息数组
			memcpy(LightData + DeviceIndex, UdpRxBuf, sizeof(tLightData));

			//清零超时
			LastLightUpdate[DeviceIndex] = 0;

			//给节点回复命令
			m_WiFiUDP.beginPacket(m_WiFiUDP.remoteIP(), m_WiFiUDP.remotePort());
			m_WiFiUDP.write((const char*)(LightCommand + DeviceIndex), sizeof(tLightCommand));
			m_WiFiUDP.endPacket(); 

			//命令发送之后，清除掉需要设置灯光的标志。
			for (byte i = 0; i < LIGHT_NUMBER; i++)
			{
				LightCommand[DeviceIndex].LightNeedSet[i] = false;
			}
		}
		break;

	default:
		break;
	}
}



bool handleFileRead(String path) {            //处理主页访问
	String contentType = "text/html";  

	if (SPIFFS.exists(path)) {                       // 如果访问的文件可以在SPIFFS中找到
		File file = SPIFFS.open(path, "r");          // 则尝试打开该文件
		WebServer.streamFile(file, contentType);	 // 并且将该文件返回给浏览器
		file.close();                                // 并且关闭文件
		return true;                                 // 返回true
	}
	return false;                                    // 如果文件未找到，则返回false
}

void handleNotFound()
{
	// 获取用户请求网址信息
	String webAddress = WebServer.uri();

	//打印出请求
	if(webAddress != "/update")
	{ 
		printf("%s\n", webAddress.c_str());
	}
	
	//如果是主页请求，则发送FLASH中的index.html文件
	if (webAddress.endsWith("/")) {                   // 如果访问地址以"/"为结尾
		webAddress = "/index.html";                     // 则将访问地址修改为/index.html便于SPIFFS访问

		// 通过handleFileRead函数处处理用户访问
		handleFileRead(webAddress);
	}
	else if (webAddress.endsWith("update"))
	{
		WebServer.send(200, "text/plain", ProcessUpdate());
	}
	else if (webAddress.startsWith("/Control"))
	{
		if (WebServer.args() == 3)
		{
			int DeviceType = WebServer.arg(0).toInt();
			int DeviceIndex = WebServer.arg(1).toInt();
			int Operation = WebServer.arg(2).toInt();

			printf("DeviceType:%d DeviceIndex:%d Operation:%d\n", DeviceType, DeviceIndex, Operation);

			PocessControl(DeviceType,DeviceIndex,Operation);
		}
		else
		{
			WebServer.send(404, "text/plain", "404 Not Found");
		}
	}
	else
	{
		WebServer.send(404, "text/plain", "404 Not Found");
	}
}

void StartWebServer()
{
	WebServer.begin();
	WebServer.onNotFound(handleNotFound);//将所有请求导向自己处理的代码
}

unsigned long LastMillis = 0;
void TenthSecondsSinceStartTask()
{
	unsigned long CurrentMillis = millis();
	if (abs(CurrentMillis - LastMillis) > 100)
	{
		LastMillis = CurrentMillis;
		TenthSecondsSinceStart++;
		OnTenthSecond();
	}
}

void OnSecond()
{
	time_t now = time(nullptr); //获取当前时间

	//转换成年月日的数字，可以更加自由的显示。
	struct   tm* timenow;
	timenow = localtime(&now);
	unsigned char tempHour = timenow->tm_hour;
	unsigned char tempMinute = timenow->tm_min;
	unsigned char tempSecond = timenow->tm_sec;
	unsigned char tempDay = timenow->tm_mday;
	unsigned char tempMonth = timenow->tm_mon + 1;
	unsigned int tempYear = timenow->tm_year + 1900;
	unsigned char tempWeek = timenow->tm_wday;


	//生成  年月日时分秒 字符串。
	sprintf(DateTimeStr, "%d-%02d-%02d %02d:%02d:%02d"
		, tempYear
		, tempMonth
		, tempDay
		, tempHour
		, tempMinute
		, tempSecond
	);

	for (byte i = 0; i < LIGHT_CONTROLER_NUMBER; i++)
	{
		LastLightUpdate[i] ++ ;
	}

}

void OnTenthSecond()
{
	if (TenthSecondsSinceStart%10 == 0)
	{
		OnSecond();
	}
}

bool SearchMacAddress(unsigned char* Id, unsigned long* Mac, unsigned long* MacList, unsigned long MaxNumber)
{
	static unsigned int Counter = 0;
	unsigned char i = 0;
	for (i = 0; i < MaxNumber; i++)
	{
		if (memcmp(Mac, &MacList[i * 6], 24) == 0)
		{
			*Id = i;
			return true;
		}
	}
	Counter++;
	printf("unknow macAddress %02X:%02X:%02X:%02X:%02X:%02X %d\r\n", Mac[0], Mac[1], Mac[2], Mac[3], Mac[4], Mac[5], Counter);

	return false;
}